import urllib.parse
from urllib.parse import urljoin
from typing import Dict, Optional, List
import asyncio
import aiohttp
import json
import orjson
import atexit

from app.base.request_form import MultiFileBuildForm, MultiFileForm
from settings.config import settings
from common.enums import CONTENT_TYPE
from common.log import log

class AsyncHttpClient():
    """可以使用异步管理的 http client
    """
    def __init__(self):
        pass
    
    @staticmethod
    def join_url(first: str, second: str):
        first = first.rstrip('/')
        second = second.lstrip('/')
        
        return urljoin(first, second)

    @staticmethod
    async def post(url: str, data: Dict = None, headers: Optional[Dict[str, str]] = None, timeout: float = None)->Dict:
        """发送POST请求
        
        Args:
            url (str): 请求的URL
            data (dict): 要发送的JSON数据（可选）
            headers (dict): 自定义请求头（可选）
        
        Returns:
            r_json (dict): 解析后的JSON响应数据
        """
        try:
            
            post_kwargs = {}
            if headers is not None:
                post_kwargs['headers'] = headers
            
            # JSON数据请求
            if data:
                # data_json = json.dumps(data)
                data_json = orjson.dumps(data).decode()
                post_kwargs['data'] = data_json
            if not headers or 'Content-Type' not in headers or headers.get('Content-Type') != 'application/json':
                # 确保只有当data为字典或列表（通常用于JSON数据），并且Content-Type不是application/json或未设置时，
                # 才设置Content-Type为application/json。
                post_kwargs['headers'] = headers.copy() if headers else {}
                post_kwargs['headers']['Content-Type'] = 'application/json'
                
            # post 请求
            async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(timeout)) as session:
                async with session.post(url=url, **post_kwargs) as response:
                    
                    r_text = await response.text()
                    try:
                        r_json = orjson.loads(r_text)
                        if response.status != 200 and response.status != 0:
                            log.error(f"Failed request url: {url} response: {r_json}")
                        r_json['status'] = response.status
                        return r_json
                    except orjson.JSONDecodeError:
                        log.error(f"Failed to decode response as JSON: {r_json}")
        except aiohttp.ClientError as e:
            log.error(f"Aiohttp request [POST] [{url}] error: {e}")
        except Exception as e:
            log.error(f"Error [POST] [{url}]: {e}")
        return None
    
    @staticmethod
    async def post_form_data(url: str, writer: aiohttp.MultipartWriter, headers: Dict, timeout: float = None):
        try:
            async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(timeout)) as session:
                async with session.post(url=url, data=writer, headers=headers) as response:
                        
                    r_text = await response.text()
                    try:
                        r_json = orjson.loads(r_text)
                        if response.status != 200 and response.status != 0:
                            log.error(f"Failed request url: {url} response: {r_json}")
                        r_json['status'] = response.status
                        return r_json
                    except orjson.JSONDecodeError:
                        log.info(f"Failed to decode response as JSON: {r_json}")
        except aiohttp.ClientError as e:
            log.error(f"Aiohttp request [POST] [{url}] error: {e}")
        except Exception as e:
            log.error(f"Error [POST] [{url}]: {e}")
        return None
       
    @staticmethod
    async def get(url: str, params: dict = None, headers: dict = None, timeout: float = None):
        """"""
        try:
            # 构建URL参数字符串，如果params不为空
            url_with_params = url + "?" + urllib.parse.urlencode(params) if params else url
                        
            # 根据headers是否为None决定是否在请求中包含headers
            get_kwargs = {}
            if headers is not None:
                get_kwargs['headers'] = headers
            async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(timeout)) as session:
                async with session.get(url=url_with_params, **get_kwargs) as response:
                    r_text = await response.text()
                    if response.status != 200 and response.status != 0:
                        log.error(f"Failed request url: {url} response: {r_json}")
                    r_json['status'] = response.status
                    r_json = orjson.loads(r_text)
                    return r_json
        except aiohttp.ClientError as e:
            log.error(f"Aiohttp request [GET] [{url}] error: {e}")
        except Exception as e:
            log.error(f"Error [GET] [{url}]: {e}")
        return None
    
    @staticmethod
    async def build_form_data(content: bytes, fname: str, param_name: str, data_to_form: List[dict] = None) -> aiohttp.MultipartWriter:
        """构建文件表单数据（单文件）

        Args:
            content (bytes): 二进制文件内容
            fname (str): 文件名 example: file.pdf
            param_name (str): 文件参数名
            data_to_form (dict): 其他表单参数

        Returns:
            aiohttp.MultipartWriter: 表单内容
        """
        writer = aiohttp.MultipartWriter()
        try:
            if data_to_form:
                for d in data_to_form:
                    if "value" not in d or "name" not in d:
                        log.error(f"表单参数格式错误，缺少 [value] 或 [name] 参数")
                        return None
                    if isinstance(d['value'], list):
                        value_str = ','.join(str(index) for index in d['value'])
                        part = writer.append(value_str)
                        part.set_content_disposition('form-data', name=d["name"])
                    else:
                        writer.append(d["value"], {'Content-Disposition': f'form-data; name="{d["name"]}"'})
            encoded_filename = urllib.parse.quote(fname, safe='')
            
            # 手动设置Content-Disposition头部
            ctype = CONTENT_TYPE.get_content_type(fname.split('.')[-1])
            if not ctype:
                log.error(f"文件类别不支持: {fname}")
                return None
            headers = {
                'Content-Disposition': f'form-data; name="{param_name}"; filename=""; filename*=UTF-8\'\'{encoded_filename}',
                'Content-Type': ctype
            }
            writer.append(content, headers=headers)
            return writer
        except Exception as e:
            log.error(f"构建表单数据出现异常：{e}")
        return None
    
    @staticmethod
    async def build_multifile_form_data(multi_data: MultiFileBuildForm) -> aiohttp.MultipartWriter:
        """构建文件表单数据（多文件）

        Args:
            multi_data (MultiFileBuildForm):
                包含多文件数据以及附加的其他表单数据
        Returns:
            aiohttp.MultipartWriter: 表单内容
        """
        writer = aiohttp.MultipartWriter()
        try:
            if multi_data.data_to_form:
                for d in multi_data.data_to_form:
                    if "value" not in d or "name" not in d:
                        log.error(f"表单参数格式错误，缺少 [value] 或 [name] 参数")
                        return None
                    if isinstance(d['value'], list):
                        value_str = ','.join(str(index) for index in d['value'])
                        part = writer.append(value_str)
                        part.set_content_disposition('form-data', name=d["name"])
                    else:
                        writer.append(d["value"], {'Content-Disposition': f'form-data; name="{d["name"]}"'})
            for file_form in multi_data.multi_files:
                encoded_filename = urllib.parse.quote(file_form.filename, safe='')
                
                # 手动设置Content-Disposition头部
                ctype = CONTENT_TYPE.get_content_type(file_form.filename.split('.')[-1])
                if not ctype:
                    log.error(f"文件类别不支持: {file_form.filename}")
                    return None
                headers = {
                    'Content-Disposition': f'form-data; name="{file_form.form_name}"; filename=""; filename*=UTF-8\'\'{encoded_filename}',
                    'Content-Type': ctype
                }
                writer.append(file_form.content, headers=headers)
            return writer
        except Exception as e:
            log.error(f"构建表单数据出现异常：{e}")
        return None